# Utilities for extracting information from relocation entries
r_sym(rel::Union{ELF32.Rel,ELF32.Rela}) = rel.r_info >> 8
r_type(rel::Union{ELF32.Rel,ELF32.Rela}) = rel.r_info % UInt8
r_sym(rel::Union{ELF64.Rel,ELF64.Rela}) = rel.r_info >> 32
r_type(rel::Union{ELF64.Rel,ELF64.Rela}) = rel.r_info % UInt32
addend(rel::Union{ELF32.Rela,ELF64.Rela}) = rel.r_addend
addend(rel::Union{ELF32.Rel,ELF64.Rel}) = 0

r_sym(x) = r_sym(deref(x))
r_type(x) = r_type(deref(x))
addend(x) = addend(deref(x))

# The relocation to apply
immutable RelocToApply
    value::UInt64
    size::UInt8
end

function compute_value(h, symbols, reloc, secs)
    # Here one could to external symbol lookups, etc, but I'm not interested
    UInt(symbolvalue(symbols[r_sym(reloc) + 1], secs)), deref(symbols[r_sym(reloc) + 1]).st_value
end

# Apply relocations in `buffer`. `h` should be the buffer being relocated
function relocate!(buffer, h; LOI = nothing)
    sects = Sections(h)
    rel_sects = filter(sects) do sec
        sht = deref(sec).sh_type
        sht == SHT_RELA || sht == SHT_REL
    end
    for sec in rel_sects
        symbols = Symbols(link_sec(sec))
        relocated_sec = info_sec(sec)
        for reloc in Relocations(sec)
            if r_type(reloc) == 0
                continue
            end
            Value, STValue = compute_value(h, symbols, reloc, sects)
            if LOI === nothing
                SectionLoadAddress = deref(relocated_sec).sh_addr
            else
                SectionLoadAddress = getSectionLoadAddress(LOI,relocated_sec)
            end
            rta = compute_relocation(h, reloc, SectionLoadAddress, Value, STValue)
            seek(buffer, deref(relocated_sec).sh_offset + deref(reloc).r_offset)
            write(buffer, rta.size == 8 ? rta.value :
                rta.size == 4 ? convert(UInt32,rta.value) :
                rta.size == 2 ? convert(UInt16,rta.value) :
                rta.size == 1 ? convert(UInt8,rta.value) :
                error("Unsupported Relocation Size"))
        end
    end
end

function compute_relocation(h, reloc, SectionLoadAddress, Value, STValue)
    if h.file.header.e_machine == EM_X86_64
        return compute_X86_64(reloc, SectionLoadAddress, Value, STValue)
    elseif h.file.header.e_machine == EM_PPC64
        return compute_PPC64(reloc, SectionLoadAddress, Value, STValue)
    else
        error("Unknown Machine!")
    end
end

## Relocation compuations

# The distinction between Value and STValue deserves some explanation.
# The added difficulty stems from the fact that we're also relocating
# relocatable object whose sections need not be contiguous in memory,
# which is an assumption that is implicitly made in the ELF relocation
# rules. Instead, we need to confront the possibility that the location
# at which we're applying the relocation and `Value` are contained in
# sections with different slides. To counteract this, `Value` is the full
# load address of the target. It is to be used in computations that demand
# comparison to other absolute addresses. `STValue` on the other hand is the
# symbol's `st_value`

function compute_X86_64(reloc, SectionLoadAddress, Value, STValue)
    # Any error generated by the conversions below indicates a bad
    # value (the conversions are checked)
    kind = r_type(reloc)
    Offset = deref(reloc).r_offset
    Addend = addend(reloc)
    Value += Addend
    STValue += Addend
    if kind == R_X86_64_64
        return RelocToApply(Value, 8)
    elseif kind == R_X86_64_32
        return RelocToApply(convert(UInt32,STValue), 4)
    elseif kind == R_X86_64_32S
        return RelocToApply(convert(Int32,reinterpret(Int,STValue)), 4)
    else
        PCOffset = Value - UInt(SectionLoadAddress + Offset)
        if kind == R_X86_64_PC64
            return RelocToApply(PCOffset, 8)
        elseif kind == R_X86_64_PC32
            return RelocToApply(convert(Int32,PCOffset), 4)
        elseif kind == R_X86_64_GLOB_DAT || kind == R_X86_64_JUMP_SLOT
            return RelocToApply(Value, 8)
        elseif kind == R_X86_64_RELATIVE
            return RelocToApply(Addend #= + LoadAddress =#, 8) 
        else
            error("Unknown relocation ($(R_X86_64[kind]))")
        end
    end
end

function compute_PPC64(reloc, SectionLoadAddress, Value, STValue)
    kind = r_type(reloc)
    Offset = deref(reloc).r_offset
    Addend = addend(reloc)%UInt64
    #Value += Addend
    #STValue += Addend
    PCOff = Value - UInt64(SectionLoadAddress + Offset)
    if kind == R_PPC64_REL64 || kind == R_PPC64_REL32
        RelocToApply(PCOff+Addend, kind == R_PPC64_REL64 ? 8 : 4)
    elseif kind == R_PPC64_REL24
        # Ok, this is a weird one. The relocation computes 24 bits worth of information,
        # but can actually support up a 26 bit, quantity. It is used in the `b[la]` instructions
        # but nowhere else. Further, the 24bit immediate is not actually byte aligned, but the
        # lower two bits of that instruction encode the [la] flags. For now, we follow LLVM's
        # lead and generate `bl` instructions with the appropriate immediate, but it would
        # be preferable to leave the lower two bits intact.
        Delta = (((PCOff+Addend)%Int64)%Int32)%UInt32
        RelocToApply(0x48000001 | (Delta & 0x03FFFFFC), 4)
    elseif kind == R_PPC64_ADDR32 || kind == R_PPC64_ADDR64
        RelocToApply(STValue + Addend, kind == R_PPC64_ADDR64 ? 8 : 4)
    else
        error("Unknown relocation ($(R_PPC64[kind]))")
    end
end

